<?php

namespace W3\Db;

use W3\Db;
use PDO;
use stdClass;

/**
 * connector class
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */
abstract class Connector
{
    /**
     * 对象引号过滤
     *
     * @access public
     * @param string $string
     * @return string
     */
    public function quote(string $string): string
    {
        return '`' . $string . '`';
    }
	
    /**
     * 判断适配器是否可用
     *
     * @access public
     * @return boolean
     */
    public static function isAvailable(): bool
    {
        return class_exists('PDO');
    }

    /**
     * Build table insert
     *
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build_insert(Query $builder): array
    {
		$parameter = [];
		$sql = '';
		$table = $builder->getQuery('table');

        if ($rows = $builder->getQuery('rows')) {
			$keys = [];
			$values = [];
            foreach ($rows as $row) 
		    {
                $keys[] = $row['escape_key'];
				!$row['escape'] && $parameter[] = $row['row'];
				$values[] = $row['escape'] ? $row['row'] : '?';
            }
			
			$sql = ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $values) . ')';
        }
		return ['INSERT INTO ' . $table . $sql, $parameter];
    }

    /**
     * Build table update
     *
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build_update(Query $builder): array
    {
		$parameter = [];
		$sql = '';
		$table = $builder->getQuery('table');

        if ($rows = $builder->getQuery('rows')) {
			$keys = [];
            foreach ($rows as $row) 
		    {
                $keys[] = $row['escape_key'] . ' = ' . ($row['escape'] ? $row['row'] : '?');
				!$row['escape'] && $parameter[] = $row['row'];
            }
			
			$sql .= implode(', ', $keys);
        }
		
		$build = $this->build($builder);
		$build[0] && $sql .= ' ' . $build[0];
		$build[1] && $parameter = array_merge($parameter, $build[1]);
		
		return ['UPDATE ' . $table . ' SET ' . $sql, $parameter];
    }

    /**
     * Build the select columns of the query
     *
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build_select(Query $builder): array
    {
		$table  = $builder->getQuery('table');
		$fields = $builder->getQuery('fields');
		list($sql, $parameter) = $this->build($builder);

        return ['SELECT ' . $fields . ' FROM ' . $table . $sql, $parameter];
    }

    /**
     * Build a delete query
     *
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build_delete(Query $builder): array
    {
		$table  = $builder->getQuery('table');
		$parameter = [];
		$sql = '';
		
		$build = $this->build($builder);
		$build[0] && $sql .= ' ' . $build[0];
		$build[1] && $parameter = array_merge($parameter, $build[1]);
		
        return ['DELETE FROM ' . $table . $sql, $parameter];
    }

    /**
     * Build a select count query
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build_select_count(Query $builder): array
    {
		$table  = $builder->getQuery('table');
		$parameter = [];
		$sql = '';
		
		$build = $this->build($builder);
		$build[0] && $sql .= ' ' . $build[0];
		$build[1] && $parameter = array_merge($parameter, $build[1]);

        return ['SELECT COUNT(*) as cnt FROM ' . $table . $sql, $parameter];
    }

    /**
     * 合成sql语句
     *
     * @access public
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build_sql(Query $builder): array
    {
		$sql = $builder->getQuery('sql');
		$bind = $builder->getParams(['sql']);
		return [$sql, $bind];
	}

    /**
     * 建立查询
     *
     * @param Query $builder 查询词法对象
     * @return []
     */
    public function build(Query $builder): array
    {
		$build = $builder->getQuery();

        $build['limit'] = isset($build['limit']) ? ' LIMIT ' . $build['limit'] : '';
        $build['offset'] = isset($build['offset']) ? ' OFFSET ' . $build['offset'] : '';

        $sql = $build['join'] . $build['where'] . $build['group'] . $build['having'] . $build['order'] . $build['limit'] . $build['offset'];
		
		$bind = $builder->getParams(['join', 'where', 'group', 'having', 'order']);
		
		return [$sql, $bind];
    }
	
    /**
     * 取出最后一次查询影响的行数
     *
     * @param Query $builder 查询词法对象
     * @return mixed
     */
    public function affected(Query $builder)
    {
		$action = $builder->getQuery('action');
		$result = false;

        switch ($action) {
            case 'UPDATE':
                list($sql, $bind) = $this->build_update($builder);
				break;
            case 'DELETE':
                list($sql, $bind) = $this->build_delete($builder);
				break;
            case 'INSERT':
                list($sql, $bind) = $this->build_insert($builder);
				break;
            case 'SQL':
                list($sql, $bind) = $this->build_sql($builder);
				break;
        }

		if (isset($sql)) {
			$start_time = microtime(TRUE);
            $stmt = $builder->db->master()->prepare($sql);
            $r = $bind ? $stmt->execute($bind) : $stmt->execute();
			
            /**if ($r) {
                $r = $stmt->rowCount();
                $result = $r !== 0 ? true : false;
            }*/
			
			$result = $stmt->rowCount();
			
		    $end_time = microtime(TRUE);
		    Db::debug($sql, $bind, ($end_time - $start_time));
		}

        return $result;
    }
	
    /**
     * 取出最后一次插入返回的主键值
     *
     * @param Query $builder 查询词法对象
     * @return integer | false
     */
    public function lastId(Query $builder)
    {
		$action = $builder->getQuery('action');
		$result = false;

        switch ($action) {
            case 'INSERT':
                list($sql, $bind) = $this->build_insert($builder);
				break;
            case 'SQL':
                list($sql, $bind) = $this->build_sql($builder);
				break;
        }
			
		if ($sql) {
			$start_time = microtime(TRUE);
		    $db = $builder->db->slaver();
            $stmt = $db->prepare($sql);
            $r = $bind ? $stmt->execute($bind) : $stmt->execute();
            if ($r) {
                $r = $stmt->rowCount();
                $result = $r !== 0 ? $db->lastInsertId() : false;
            }
		    $end_time = microtime(TRUE);
		    Db::debug($sql, $bind, ($end_time - $start_time));
		}
		
        return $result;
    }
	
    /**
     * 一次取出一行
     *
     * @param Query $builder 查询词法对象
     * @param array $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
     * @return mixed
     */
    public function fetch(Query $builder, ?callable $filter = NULL): ?array
    {
		$action = $builder->getQuery('action');
		$result = NULL;

        switch ($action) {
            case 'SELECT':
                list($sql, $bind) = $this->build_select($builder);
				break;
            case 'SQL':
                list($sql, $bind) = $this->build_sql($builder);
				break;
        }

		if ($sql) {
            $start_time = microtime(TRUE);
            $stmt = $builder->db->slaver()->prepare($sql);

            $r = $bind ? $stmt->execute($bind) : $stmt->execute();
            if ($r) {
				$rows = $stmt->fetch(PDO::FETCH_ASSOC);
                $result = $rows 
				    ? ($filter ? call_user_func($filter, $rows) : $rows)
					: NULL;
            }
		    $end_time = microtime(TRUE);
		    Db::debug($sql, $bind, ($end_time - $start_time));
		}
        return $result;
    }

    /**
     * 一次取出多行
     *
     * @param Query $builder 查询词法对象
     * @param array $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
     * @return mixed
     */
    public function get(Query $builder, ?callable $filter = NULL): array
    {
		$action = $builder->getQuery('action');
		$result = [];

        switch ($action) {
            case 'SELECT':
                list($sql, $bind) = $this->build_select($builder);
				break;
            case 'SQL':
                list($sql, $bind) = $this->build_sql($builder);
				break;
        }
	
		$start_time = microtime(TRUE);
        $stmt = $builder->db->slaver()->prepare($sql);

        $r = $bind ? $stmt->execute($bind) : $stmt->execute();
        if ($r) {
			
			$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
			
			$filter && $result = array_map($filter, $result);
        }
	    $end_time = microtime(TRUE);
		Db::debug($sql, $bind, ($end_time - $start_time));

        return $result;
    }
	
    /**
     * 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
     *
     * @param Query $builder 查询词法对象
     * @param array $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
     * @return object
     */
    public function object(Query $builder, ?array $filter = NULL): ?object
    {
        $result = NULL;
		$action = $builder->getQuery('action');

        switch ($action) {
            case 'SELECT':
                list($sql, $bind) = $this->build_select($builder);
				break;
            case 'SQL':
                list($sql, $bind) = $this->build_sql($builder);
				break;
        }

		if ($sql) {
            $start_time = microtime(TRUE);
            $stmt = $builder->db->slaver()->prepare($sql);

            $r = $bind ? $stmt->execute($bind) : $stmt->execute();
            if ($r) {
				
				$rows = $stmt->fetch(PDO::FETCH_OBJ);
                $result = $rows 
				    ? ($filter ? call_user_func($filter, $rows) : $rows)
					: NULL;
            }
		    $end_time = microtime(TRUE);
		    Db::debug($sql, $bind, ($end_time - $start_time));
		}
        return $result;
    }		
	
    /**
     * 对数据库查询运行计数函数
     *
     * @param Query $builder 查询词法对象
     * @return int
     */
    public function count(Query $builder)
    {
        $result = [];
        list($sql, $bind) = $this->build_select_count($builder);
		
		$start_time = microtime(TRUE);
        $stmt = $builder->db->slaver()->prepare($sql);

        $r = $bind
  		    ? $stmt->execute($bind)
			: $stmt->execute();
			
        if ($r) {
			$result = $stmt->fetch(PDO::FETCH_ASSOC);
        }
		
	    $end_time = microtime(TRUE);
		Db::debug($sql, $bind, ($end_time - $start_time));

        return empty($result['cnt']) ? 0 : $result['cnt'];
    }
}
